using System.Globalization;

namespace Core.Base
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using Contracts.Enums;
    using Contracts.Interfaces;
    using System.Threading.Tasks;
    using System.Diagnostics;
    using System.Threading;

    /// <summary>
    /// Base robot class
    /// </summary>
    public abstract class RobotBase : PropertyChangedBase, IRobot, IEquatable<RobotBase>
    {
        private readonly string _name;
        private readonly string _description;
        private readonly FactionsEnum _faction;
        private readonly RobotTechnologyLevel _technologyLevel;
        private readonly RobotSizesEnum _size;
        private readonly ISlot[] _slots;
        private readonly IFeature<object>[] _features;
        private readonly IBonusModifier[] _bonuses;
        private IAgent _agent;
        private IEnumerable<ICondition<IExtension>> _conditions;

        /// <summary>
        /// Create a new instance of the <see cref="T:Core.Base.RobotBase"/> class.
        /// </summary>
        protected RobotBase(){}
        /// <summary>
        /// Create a new instance of the <see cref="T:Core.Base.RobotBase"/> class.
        /// </summary>
        protected RobotBase(string name, string description, FactionsEnum faction, RobotTechnologyLevel technologyLevel, RobotSizesEnum size, ISlot[] slots, IFeature<object>[] features, IBonusModifier[] bonuses, IEnumerable<ICondition<IExtension>> conditions)
            : this()
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
            if (faction == FactionsEnum.NotDefined)throw new InvalidEnumArgumentException("faction");
            if (technologyLevel == RobotTechnologyLevel.NotDefined) throw new InvalidEnumArgumentException("technologyLevel");
            if (size==RobotSizesEnum.NotDefined)throw new InvalidEnumArgumentException("size");

            _name = name;
            _description = description;
            _faction = faction;
            _technologyLevel = technologyLevel;
            _size = size;
            _slots = slots ?? new ISlot[] {};
            _features = features ?? new IFeature<object>[] {};
            _bonuses = bonuses ?? new IBonusModifier[] {};
            _conditions = conditions;
        }
        /// <summary>
        /// Obtains the robot modules
        /// </summary>
        public IEnumerable<IModule> Modules
        {
            get { return Slots.Where(s=>s.HasModule).Select(s=>s.FittedModule)  ; }
        }
        /// <summary>
        /// Obtains or defines the robot name
        /// </summary>
        public string Name
        {
            get { return _name; }
        }
        /// <summary>
        /// Obtains or defines the robot description
        /// </summary>
        public string Description
        {
            get { return _description; }
        }
        /// <summary>
        /// Obtain sor defines the robot faction
        /// </summary>
        public FactionsEnum Faction
        {
            get { return _faction; }
        }
        /// <summary>
        /// Obtains or defines the robot technology level
        /// </summary>
        public RobotTechnologyLevel TechnologyLevel
        {
            get { return _technologyLevel; }
        }
        /// <summary>
        /// Obtains or defines the robot size
        /// </summary>
        public RobotSizesEnum Size
        {
            get { return _size; }
        }
        /// <summary>
        /// Obtains or defines the robot slots
        /// </summary>
        public IEnumerable<ISlot> Slots
        {
            get { return _slots; }
        }
        /// <summary>
        /// Obtains or defines the features
        /// </summary>
        public IEnumerable<IFeature<object>> Features
        {
            get { return _features; }
        }
        /// <summary>
        /// Determines if there is any features
        /// </summary>
        public bool HasFeatures
        {
            get { return _features != null && _features.Any(); }
        }
        /// <summary>
        /// Obtains the robot bonuses
        /// </summary>
        public IEnumerable<IBonusModifier> Modifiers
        {
            get { return _bonuses; }
        }
        /// <summary>
        /// Determines if the extension has conditions
        /// </summary>
        public bool HasConditions
        {
            get { return Conditions != null && Conditions.Any(); }
        }
        /// <summary>
        /// Obtains or defines the extension conditions
        /// </summary>
        public IEnumerable<ICondition<IExtension>> Conditions
        {
            get { return _conditions; }
            set { _conditions = value; }
        }
        /// <summary>
        /// Obtains the robot engine features
        /// </summary>
        public IEnumerable<IFeature<object>> EngineFeatures
        {
            get { return this.Features(f => f.FeatureGroup == FeatureGroupsEnum.Engine).OrderBy(f => f.Name); }
        }
        /// <summary>
        /// Obtains the robot defense features
        /// </summary>
        public IEnumerable<IFeature<object>> DefenceFeatures
        {
            get { return this.Features(f => f.FeatureGroup == FeatureGroupsEnum.Defence); }
        }
        /// <summary>
        /// Obtains the robot offence features
        /// </summary>
        public IEnumerable<IFeature<object>> OffenceFeatures
        {
            get { return this.Features(f => f.FeatureGroup == FeatureGroupsEnum.Offence); }
        }
        /// <summary>
        /// Obtains the robot general features
        /// </summary>
        public IEnumerable<IFeature<object>> GeneralFeatures
        {
            get { return this.Features(f => f.FeatureGroup == FeatureGroupsEnum.General).OrderBy(f => f.Name); }
        }
        /// <summary>
        /// Obtains the robot Targeting Equipment features
        /// </summary>
        public IEnumerable<IFeature<object>> TargetingEquipmentFeatures
        {
            get { return this.Features(f => f.FeatureGroup == FeatureGroupsEnum.TargetingEquipment).OrderBy(f => f.Name); }
        }
        /// <summary>
        /// Obtains the robot slots features
        /// </summary>
        public IEnumerable<IFeature<object>> SlotFeatures
        {
            get { return this.Features(f => f.FeatureGroup == FeatureGroupsEnum.Slots).OrderBy(f => f.Name); }
        }
        /// <summary>
        /// Obtains the fitted head modules
        /// </summary>
        public IEnumerable<IModule> HeadModules
        {
            get { return Slots.Where(s => s.HasModule && s.SlotGroup == SlotGroupsEnum.Head).OrderBy(s => s.SortOrder).Select(s => s.FittedModule); }
        }
        /// <summary>
        /// Obtains the fitted chassis modules
        /// </summary>
        public IEnumerable<IModule> ChassisModules
        {
            get { return Slots.Where(s => s.HasModule && s.SlotGroup == SlotGroupsEnum.Chassis).OrderBy(s => s.SortOrder).Select(s => s.FittedModule); }
        }
        /// <summary>
        /// Obtains the fitted leg modules
        /// </summary>
        public IEnumerable<IModule> LegModules
        {
            get { return Slots.Where(s => s.HasModule && s.SlotGroup == SlotGroupsEnum.Legs).OrderBy(s => s.SortOrder).Select(s => s.FittedModule); }
        }
        /// <summary>
        /// Obtains the total cpu used by modules
        /// </summary>
        public virtual double TotalCpuUsage
        {
            get
            {
                double cpuUsage = 0;
                cpuUsage = Modules
                    .Select(module => module.Features.Where(f =>
                                                            f.FeatureType == FeaturesEnum.CpuUsage ||
                                                            f.FeatureType == FeaturesEnum.EcmCpuUsage ||
                                                            f.FeatureType == FeaturesEnum.IndustrialCpuUsage ||
                                                            f.FeatureType == FeaturesEnum.BuildingCpuUsage ||
                                                            f.FeatureType == FeaturesEnum.EngineeringCpuUsage ||
                                                            f.FeatureType == FeaturesEnum.SignalDetectorCpuUsage ||
                                                            f.FeatureType == FeaturesEnum.SignalMaskerCpuUsage ||
                                                            f.FeatureType == FeaturesEnum.WeaponCpuUsage ||
                                                            f.FeatureType == FeaturesEnum.ArmorModuleCpuUsage ||
                                                            f.FeatureType == FeaturesEnum.ShieldHardenerCpuUsage ||
                                                            f.FeatureType == FeaturesEnum.ShieldCpuUsage).FirstOrDefault())
                    .Where(cpu => cpu != null)
                    .Aggregate(cpuUsage,
                               (current, cpu) => current + 
                                   (cpu.IsModified ? (double) cpu.ModifiedValue : (double) cpu.Value));

                return Math.Round(cpuUsage,2);
            }
        }
        /// <summary>
        /// Obtains the maximum cpu capacity
        /// </summary>
        public virtual double CpuCapacity
        {
            get
            {
                return this.Features.Where(f => f.FeatureType == FeaturesEnum.CpuPerformance).FirstOrDefault().IsModified
                        ? (double)this.Features.Where(f => f.FeatureType == FeaturesEnum.CpuPerformance).FirstOrDefault().ModifiedValue
                        : (double)this.Features.Where(f => f.FeatureType == FeaturesEnum.CpuPerformance).FirstOrDefault().Value;
            }
        }
        /// <summary>
        /// Obtains the available CPU
        /// </summary>
        public virtual double AvailableCpu
        {
            get { return Math.Round(CpuCapacity - TotalCpuUsage,2); }
        }
        /// <summary>
        /// Obtains the percentage of cpu used
        /// </summary>
        public virtual double CpuUsagePercentage
        {
            get { return Math.Round(TotalCpuUsage / CpuCapacity * 100,0); }
        }
        /// <summary>
        /// Obtains the formatted cpu percentage usage value
        /// </summary>
        public string CpuUsagePercentageString
        {
            get { return string.Format(CultureInfo.CurrentCulture, "{0:P0}", CpuUsagePercentage / 100); }
        }
        /// <summary>
        /// Obtains the maximum reactor capacity
        /// </summary>
        public virtual double ReactorCapacity
        {
            get
            {
                return this.Features.Where(f => f.FeatureType == FeaturesEnum.ReactorPerformance).FirstOrDefault().IsModified
                    ? (double)this.Features.Where(f => f.FeatureType == FeaturesEnum.ReactorPerformance).FirstOrDefault().ModifiedValue
                    : (double)this.Features.Where(f => f.FeatureType == FeaturesEnum.ReactorPerformance).FirstOrDefault().Value;
            }
        }
        /// <summary>
        /// Obtains the total reactor used by modules
        /// </summary>
        public virtual double TotalReactorUsage
        {
            get 
            {
                double total = 0;
                total = Modules
                    .Select(module => module.Features.Where(f =>
                        f.FeatureType == FeaturesEnum.ReactorUsage ||
                        f.FeatureType == FeaturesEnum.IndustrialReactorUsage ||
                        f.FeatureType == FeaturesEnum.BuildingReactorUsage ||
                        f.FeatureType == FeaturesEnum.WeaponReactorUsage ||
                        f.FeatureType == FeaturesEnum.ArmorModuleReactorUsage ||
                        f.FeatureType == FeaturesEnum.ShieldHardenerReactorUsage ||
                        f.FeatureType == FeaturesEnum.ShieldReactorUsage).FirstOrDefault())
                    .Where(reactor => reactor != null)
                    .Aggregate(total, (current, reactor) => current + (reactor.IsModified ? (double)reactor.ModifiedValue : (double)reactor.Value));

                return Math.Round(total,2);
            }
        }
        /// <summary>
        /// Obtains the percentage of reactor used by modules
        /// </summary>
        public virtual double ReactorUsagePercentage
        {
            get { return Math.Round(TotalReactorUsage/ReactorCapacity*100,0); }
        }
        /// <summary>
        /// Obtains the formatted reactor percentage usage value
        /// </summary>
        public string ReactorUsagePercentageString
        {
            get { return string.Format(CultureInfo.CurrentCulture, "{0:P0}", ReactorUsagePercentage / 100); }
        }
        /// <summary>
        /// Obtains the available reactor
        /// </summary>
        public virtual double AvailableReactor
        {
            get { return Math.Round(ReactorCapacity - TotalReactorUsage,2); }
        }
        /// <summary>
        /// Obtains the total accumulator used per second
        /// </summary>
        public double? TotalAccumulatorUsagePerSecond
        {
            get { return Modules.Cast<ModuleBase>().Sum(m => m.AccumulatorUsagePerSecond); }
        }
        /// <summary>
        /// Obtains the base accumulator amount recharged per second
        /// </summary>
        public double AccumulatorRechargedPerSecondBase
        {
            get
            {
                var accFeature = this.Features.Where(f => f.FeatureType == FeaturesEnum.AccumulatorCapacity).FirstOrDefault();
                if(accFeature == null)return 0;

                var rechFeature = this.Features.Where(f => f.FeatureType == FeaturesEnum.AccumulatorRechargeTime).FirstOrDefault();
                if (rechFeature == null) return 0;

                var result =
                    (double)(accFeature.IsModified ? accFeature.ModifiedValue : accFeature.Value)/
                    (double)(rechFeature.IsModified ? rechFeature.ModifiedValue : rechFeature.Value);

                return Math.Round(result,2, MidpointRounding.AwayFromZero);
            }
        }
        /// <summary>
        /// Determines if the accumulator recharged per second is superior to consumption
        /// </summary>
        public bool IsAccumulatorStable
        {
            get { return AccumulatorRechargedPerSecond > 0; }
        }
        /// <summary>
        /// Obtains the accumulator recharge per second minus the consumption
        /// </summary>
        public double AccumulatorRechargedPerSecond
        {
            get
            {
                return  Math.Round( AccumulatorRechargedPerSecondBase - (TotalAccumulatorUsagePerSecond.HasValue ? TotalAccumulatorUsagePerSecond.Value : 0),2);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public double AccumulatorStabilityPercentage
        {
            get
            {
                var accrecharge =  Math.Round(AccumulatorRechargedPerSecond / AccumulatorRechargedPerSecondBase,2);
                if (accrecharge > 1) return 1;

                else return accrecharge;
            }
        }
        /// <summary>
        /// Obtains the accumulator duration
        /// </summary>
        public TimeSpan? AccumulatorLifeSpan
        {
            get
            {
                var accFeature = this.Features.Where(f => f.FeatureType == FeaturesEnum.AccumulatorCapacity).FirstOrDefault();
                if (accFeature == null) return TimeSpan.Zero;

                var accCapacity =(double)(accFeature.IsModified ? accFeature.ModifiedValue : accFeature.Value);

                return AccumulatorRechargedPerSecond < 0
                    ? TimeSpan.FromSeconds(accCapacity / (Math.Abs(AccumulatorRechargedPerSecond)))
                    : TimeSpan.Zero;
            }
        }
        /// <summary>
        /// Obtains or defines the current robot pilot
        /// </summary>
        public IAgent Pilot
        {
            get { return _agent; }
            set
            {
                if(_agent!=null)
                    _agent.PropertyChanged -= OnAgentPropertyChanged;

                _agent = value;

                if(_agent!=null)
                {
                    _agent.PropertyChanged += OnAgentPropertyChanged;
                    Task.Factory.StartNew(
                        () => CalculateFeatures(),
                        CancellationToken.None,
                        TaskCreationOptions.None,
                        TaskScheduler.FromCurrentSynchronizationContext());
                }
            }
        }
        /// <summary>
        /// Obtains the total count of turret slots (light or medium)
        /// </summary>
        public int TotalTurretSlotsCount
        {
            get { return GetSlotsCount(SlotGroupsEnum.Chassis, SlotTypesEnum.Turret, false); }
        }
        /// <summary>
        /// Obtains the total count of free turret slots (light or medium)
        /// </summary>
        public int AvailableTurretSlotsCount
        {
            get { return GetSlotsCount(SlotGroupsEnum.Chassis, SlotTypesEnum.Turret, true); }
        }
        /// <summary>
        /// Obtains the total count of missile slots (light or medium)
        /// </summary>
        public int TotalMissileSlotsCount
        {
            get { return GetSlotsCount(SlotGroupsEnum.Chassis, SlotTypesEnum.Missile, false); }
        }
        /// <summary>
        /// Obtains the total count of free missile slots (light or medium)
        /// </summary>
        public int AvailableMissileSlotsCount
        {
            get { return GetSlotsCount(SlotGroupsEnum.Chassis, SlotTypesEnum.Missile, true); }
        }
        /// <summary>
        /// Obtains the total count of industrial slots (light or medium)
        /// </summary>
        public int TotalIndustrialSlotsCount
        {
            get { return GetSlotsCount(SlotGroupsEnum.Chassis, SlotTypesEnum.Industrial, false); }
        }
        /// <summary>
        /// Obtains the total count of free Industrial slots (light or medium)
        /// </summary>
        public int AvailableIndustrialSlotsCount
        {
            get { return GetSlotsCount(SlotGroupsEnum.Chassis, SlotTypesEnum.Industrial, true); }
        }
        /// <summary>
        /// Obtains the total count of misc. slots (light or medium)
        /// </summary>
        public int TotalMiscellaneousSlotsCount
        {
            get { return GetSlotsCount(SlotGroupsEnum.Chassis, SlotTypesEnum.Misc, false); }
        }
        /// <summary>
        /// Obtains the total count of free misc. slots (light or medium)
        /// </summary>
        public int AvailableMiscellaneousSlotsCount
        {
            get { return GetSlotsCount(SlotGroupsEnum.Chassis, SlotTypesEnum.Misc, true); }
        }
        /// <summary>
        /// Obtains the total head slots count
        /// </summary>
        public int TotalHeadSlotsCount
        {
            get { return GetSlotsCount(SlotGroupsEnum.Head, SlotTypesEnum.NotDefined, false); }
        }
        /// <summary>
        /// Obtains the total legs slots count
        /// </summary>
        public int TotalLegsSlotsCount
        {
            get { return GetSlotsCount(SlotGroupsEnum.Legs, SlotTypesEnum.NotDefined, false); }
        }
        /// <summary>
        /// Obtains the total chassis slots count
        /// </summary>
        public int TotalChassisSlotsCount
        {
            get
            {
                return GetSlotsCount(
                    SlotGroupsEnum.Chassis, 
                    SlotTypesEnum.NotDefined, 
                    false);
            }
        }



        public double? TotalDps
        {
            get
            {
                if(ChassisModules.Any())
                {
                    var robotDamage = new RobotDamage();

                    double temp = 0;

                    foreach (var module in ChassisModules.Where(m=>m.HasAmmunitionsLoaded))
                    {
                        var feature = module.Features(f => f.FeatureType == FeaturesEnum.SmallMagneticWeaponDamage).FirstOrDefault();
                        if(feature !=null)
                        {
                            var moduledmg = feature.IsModified ? (double)feature.ModifiedValue / 100 : (double)feature.Value / 100;
                            
                            var kinetic = module.Ammunitions.Features(f => f.FeatureType == FeaturesEnum.KineticDamage).FirstOrDefault();
                            var seismic = module.Ammunitions.Features(f => f.FeatureType == FeaturesEnum.SeismicDamage).FirstOrDefault();
                            var thermal = module.Ammunitions.Features(f => f.FeatureType == FeaturesEnum.ThermalDamage).FirstOrDefault();
                            var chemical = module.Ammunitions.Features(f => f.FeatureType == FeaturesEnum.ChemicalDamage).FirstOrDefault();

                            var kinDamage = kinetic == null ? 0 : kinetic.IsModified
                                                ? (double) kinetic.ModifiedValue
                                                : (double) kinetic.Value;

                            var seismicDamage = seismic == null ? 0 : seismic.IsModified
                                                ? (double)seismic.ModifiedValue
                                                : (double)seismic.Value;

                            var thermalDamage = thermal == null ? 0 : thermal.IsModified
                                                ? (double)thermal.ModifiedValue
                                                : (double)thermal.Value;

                            var chemicalDamage = chemical == null ? 0 : chemical.IsModified
                                                ? (double)chemical.ModifiedValue
                                                : (double)chemical.Value;


                            robotDamage.KineticDamage += kinDamage * moduledmg;
                            robotDamage.SeismicDamage += seismicDamage * moduledmg;
                            robotDamage.ThermalDamage += thermalDamage * moduledmg;
                            robotDamage.ChemicalDamage += chemicalDamage * moduledmg;

                            var cycleTime = module.CycleTime;

                            var dmg = kinDamage + seismicDamage + thermalDamage + chemicalDamage;
                            if(dmg > 0 && cycleTime.HasValue)
                                temp += (moduledmg / cycleTime.Value ) * dmg;
                        }
                    }
                    return temp;
                }

                return 0;
            }
        }


        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(RobotBase other)
        {
            if (ReferenceEquals(null, other)) return false;
            return ReferenceEquals(this, other) || Equals(other._name, _name);
        }
        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>. </param><filterpriority>2</filterpriority>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            return obj.GetType() == typeof (RobotBase) && Equals((RobotBase) obj);
        }
        /// <summary>
        /// Serves as a hash function for a particular type. 
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override string ToString()
        {
            return _name;
        }
        /// <summary>
        /// Adds a module
        /// </summary>
        /// <param name="module">The IModule instance to add</param>
        /// <param name="recalc">True to recalculate the features values</param>
        public bool AddModule(IModule module, bool recalc)
        {
            if(module == null) return false;
            
            // Only 1 LWF allowed;
            if (module.Group == ModuleGroupsEnum.LightweightFrames && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.LightweightFrames).Count() == 1)
            {
                Trace.TraceWarning("Only one Lightweight frame allowed");
                return false;
            }

            // Only 1 Shield Generator allowed;
            if (module.Group == ModuleGroupsEnum.ShieldGenerators && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.ShieldGenerators).Count() == 1)
            {
                Trace.TraceWarning("Only one Shield generator allowed");
                return false;
            }

            // Only 1 Evasive module allowed;
            if (module.Group == ModuleGroupsEnum.EvasiveModules && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.EvasiveModules).Count() == 1)
            {
                Trace.TraceWarning("Only one Evasive module allowed");
                return false;
            }

            // Only 1 Energy Injector allowed;
            if (module.Group == ModuleGroupsEnum.EnergyInjectors && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.EnergyInjectors).Count() == 1)
            {
                Trace.TraceWarning("Only one Energy injector allowed");
                return false;
            }

            // Only 1 Erp allowed;
            if (module.Group == ModuleGroupsEnum.Erp && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.Erp).Count() == 1)
            {
                Trace.TraceWarning("Only one Erp allowed");
                return false;
            }

            // Only 1 Cargo Scanner allowed;
            if (module.Group == ModuleGroupsEnum.CargoScanners && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.CargoScanners).Count() == 1)
            {
                Trace.TraceWarning("Only one Cargo scanner allowed");
                return false;
            }

            // Only 1 Chassis Scanner allowed;
            if (module.Group == ModuleGroupsEnum.ChassisScanners && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.ChassisScanners).Count() == 1)
            {
                Trace.TraceWarning("Only one Chassis scanner allowed");
                return false;
            }

            // Only 1 Signal detector allowed;
            if (module.Group == ModuleGroupsEnum.SignalDetectors && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.SignalDetectors).Count() == 1)
            {
                Trace.TraceWarning("Only one Signal detector allowed");
                return false;
            }

            // Only 1 Signal masker allowed;
            if (module.Group == ModuleGroupsEnum.SignalMaskers && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.SignalMaskers).Count() == 1)
            {
                Trace.TraceWarning("Only one Signal masker allowed");
                return false;
            }

            // Only 1 Sap hacking module allowed;
            if (module.Group == ModuleGroupsEnum.SapHackingModules && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.SapHackingModules).Count() == 1)
            {
                Trace.TraceWarning("Only one Sap hacking module allowed");
                return false;
            }

            // Only 1 Interference module allowed;
            if (module.Group == ModuleGroupsEnum.InterferenceModules && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.InterferenceModules).Count() == 1)
            {
                Trace.TraceWarning("Only one Interference module allowed");
                return false;
            }

            // Only 1 Geoscanner allowed;
            if (module.Group == ModuleGroupsEnum.Geoscanners && this.Slots.Where(s => s.FittedModule != null && s.FittedModule.Group == ModuleGroupsEnum.Geoscanners).Count() == 1)
            {
                Trace.TraceWarning("Only one Geoscanner allowed");
                return false;
            }

            var freeSlot = module.SlotGroup ==  SlotGroupsEnum.Chassis 
                ? this.Slots.Where(s =>
                        s.SlotGroup == SlotGroupsEnum.Chassis &&
                        s.Fittable.HasFlag(module.SlotType) &&
                        s.Sizes.HasFlag(module.Size) &&
                        s.FittedModule == null).FirstOrDefault()
                : this.Slots.Where(s =>
                        s.SlotGroup == module.SlotGroup &&
                        s.Fittable.HasFlag(module.SlotType) &&
                        s.FittedModule == null).FirstOrDefault();
            
            if (freeSlot == null)
            {
                Trace.TraceWarning("No free slot found");
                return false;
            }

                       
            // set module active by default for some modules
            module.IsActive = module.ActivationType == ModuleActivationTypesEnum.Active && (
                module.Group == ModuleGroupsEnum.ArmorHardeners ||
                module.Group == ModuleGroupsEnum.ShieldHardeners ||
                module.Group == ModuleGroupsEnum.Eccms ||
                module.Group == ModuleGroupsEnum.SensorAmplifiers ||
                module.Group == ModuleGroupsEnum.ShieldGenerators);

            module.PropertyChanged += OnModulePropertyChanged;
            freeSlot.FittedModule = module;

            if (recalc)
                Task.Factory.StartNew(
                    () => CalculateFeatures(),
                    CancellationToken.None,
                    TaskCreationOptions.None,
                    TaskScheduler.FromCurrentSynchronizationContext());

            return true;
        }
        /// <summary>
        /// Removes a module
        /// </summary>
        /// <param name="module">The IModule instance to remove</param>
        /// <param name="recalc">True to recalculate the features values</param>
        public void RemoveModule(IModule module, bool recalc)
        {
            if (module == null) return;

            module.PropertyChanged -= OnModulePropertyChanged;

            var slot = _slots.Where(s => s.FittedModule == module).FirstOrDefault();
            if (slot != null)
                slot.FittedModule = null;

            if (recalc)
                Task.Factory.StartNew(
                    () => CalculateFeatures(),
                    CancellationToken.None,
                    TaskCreationOptions.None,
                    TaskScheduler.FromCurrentSynchronizationContext());

        }
        /// <summary>
        /// Removes all modules
        /// </summary>
        public void ClearModules()
        {
            foreach (var module in Slots.Where(s => s.FittedModule != null).Select(s => s.FittedModule))
                module.PropertyChanged -= OnModulePropertyChanged;

            foreach (var slot in Slots)
                slot.FittedModule = null;

            Task.Factory.StartNew(
                () => CalculateFeatures(),
                CancellationToken.None,
                TaskCreationOptions.None,
                TaskScheduler.FromCurrentSynchronizationContext());
        }
        /// <summary>
        /// Obtains the total available slots count
        /// </summary>
        /// <param name="group">a SlotGroupsEnum value</param>
        /// <param name="type">a SlotTypesEnum value</param>
        /// <param name="free">True to count only free slots</param>
        /// <returns></returns>
        private int GetSlotsCount(SlotGroupsEnum group, SlotTypesEnum type, bool free)
        {
            var slots = Slots.Where(s => s.SlotGroup == group);

            if (type != SlotTypesEnum.NotDefined)
                slots = slots.Where(s => s.Fittable.HasFlag(type));

            if (free)
                slots = slots.Where(s => s.FittedModule == null);

            return slots.Count();
        }
        /// <summary>
        /// Module property changed event handler
        /// </summary>
        /// <param name="sender">The IModule instance</param>
        /// <param name="e"></param>
        private void OnModulePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!string.Equals(e.PropertyName, "IsActive") && 
                !string.Equals(e.PropertyName, "Ammunitions")) return;

            if (string.Equals(e.PropertyName, "Ammunitions"))
                NotifyPropertyChange(e.PropertyName); // Raises propertychange so the Fitting instance encapsulating the robot sets IsDirty to true
            
            Task.Factory.StartNew(
                () => CalculateFeatures(string.Equals(e.PropertyName, "IsActive")),
                CancellationToken.None,
                TaskCreationOptions.None,
                TaskScheduler.FromCurrentSynchronizationContext());
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnAgentPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Task.Factory.StartNew(
                () => CalculateFeatures(),
                CancellationToken.None,
                TaskCreationOptions.None,
                TaskScheduler.FromCurrentSynchronizationContext());
        }
        /// <summary>
        /// Calculate all features modified values
        /// </summary>
        private void CalculateFeatures(bool refresh = true)
        {

            // Apply extensions and modules modifiers to robot and modules features
            foreach (var feature in Modules.SelectMany(m => m.Features))
            {
                feature.SetModifiedValue(_agent, this);
            }

            // apply robot bonuses modifiers to modules features
            foreach (var botFeature in _features)
            {
                botFeature.SetModifiedValue(_agent, this);
            }

            // Apply extensions and robot modifiers to ammunition features ( ex: PropellantMixing -> missile optimal )
            foreach (var feature in Modules
                .Where(m => m.CanHaveAmmunitions && m.HasAmmunitionsLoaded)
                .Select(m => m.Ammunitions)
                .SelectMany(a => a.Features))
            {
                feature.SetModifiedValue(_agent, this);
            }

            // Apply weapon module modifiers to ammo (e.g optimal range modifiers))
            foreach (var module in Modules.Where(m => m.CanHaveAmmunitions && m.HasAmmunitionsLoaded))
            {
                // select module ammo modifiers
                var mods = module.Modifiers.Where(m => m.ModifiedFeatureType == FeatureTypesEnum.Ammo);
                if(!mods.Any()) continue;

                foreach (var modifier in mods)
                {
                    var featureModifier = modifier;
                    
                    var ammoFeatures = module.Ammunitions.Features(f => f.FeatureType == featureModifier.Feature);
                    if(!ammoFeatures.Any()) continue;

                    foreach (var ammoFeature in ammoFeatures)
                        ammoFeature.ModifiedValue =  (ammoFeature.IsModified ? (double) ammoFeature.ModifiedValue :  (double) ammoFeature.Value) * (1 + featureModifier.Value);
                }
            }

            // Apply ammo modifiers to modules (e.g optimal range modifiers)
            foreach (var module in Modules.Where(m=>m.CanHaveAmmunitions && m.HasAmmunitionsLoaded))
            {
                var modifiers = module.Ammunitions.Modifiers.Where(m => m.ModifiedFeatureType == FeatureTypesEnum.Module);
                if(!modifiers.Any())continue;

                foreach (var modifier in modifiers)
                {
                    var ammoModifier = modifier;
                    foreach (var feature in module.Features(f => f.FeatureType == ammoModifier.Feature))
                    {
                        var temp = feature.IsModified ? feature.ModifiedValue : feature.Value;
                        if (temp == null) continue;

                        switch (ammoModifier.Unit)
                        {
                            case UnitsEnum.Percentage :
                                temp = (double)temp + (ammoModifier.Value * (double)temp);
                                break;
                            case UnitsEnum.Meters :
                                temp = (double)temp + ammoModifier.Value; 
                                break;

                            case UnitsEnum.AccumulatorPoints:
                                temp = (double)temp + ammoModifier.Value;
                                break;
                        }

                        feature.ModifiedValue = temp;
                    }    
                }
            }

            // Apply missile optimal range to launchers
            foreach(var launcher in ChassisModules.Where( m => m.Group == ModuleGroupsEnum.MissileLaunchers && m.Ammunitions!=null ))
            {
                var launcherOptimal = launcher.Features(f => f.FeatureType == FeaturesEnum.MissileLauncherOptimal).FirstOrDefault();
                if (launcherOptimal == null) continue;

                var missileOptimal = launcher.Ammunitions.Features(f => f.FeatureType == FeaturesEnum.MissileOptimal).FirstOrDefault();

                launcherOptimal.ModifiedValue = missileOptimal == null 
                    ? null 
                    : missileOptimal.IsModified ? missileOptimal.ModifiedValue :  missileOptimal.Value;
            }
            
            // refresh modules features values display
            foreach (var module in Modules.AsParallel())
                module.Refresh();


            if (refresh)
                this.Refresh();
        }

        #region ICloneable Members

        public object Clone()
        {
            var robot = (RobotBase)Activator.CreateInstance(this.GetType());
            foreach (var module in Modules)
            {
                robot.AddModule(module.Clone() as IModule, false);    
            }

            robot.Pilot = _agent;
            return robot;
        }

        #endregion
        
    }
}